/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Xml;
using DaveSexton.DocProject.Sandcastle.TopicManagement;
using DaveSexton.DocProject.Sandcastle.ContentManagement;

namespace DaveSexton.DocProject.Sandcastle.Configuration
{
	public sealed class DocumentationTagsElement : ConfigurationElement
	{
		#region Public Properties
		public IEnumerable<string> FormatTags
		{
			get
			{
				return formatTags;
			}
		}

		public IEnumerable<XmlCommentSection> Sections
		{
			get
			{
				return sections;
			}
		}
		#endregion

		#region Private / Protected
		private IEnumerable<XmlCommentSection> sections;
		private IEnumerable<string> formatTags;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="DocumentationTagsElement" /> class.
		/// </summary>
		public DocumentationTagsElement()
		{
		}
		#endregion

		#region Methods
		private static IEnumerable<string> ReadFormatTags(XmlReader reader)
		{
			// NOTE: The yield statement must not be used here because the compiler will
			// generate a class that references the reader.  This method will not actually 
			// get processed until the formatTags field is used (it holds the return value), 
			// and the reader will be invalid at that time.
			List<string> list = new List<string>(16);

			reader.ReadStartElement();

			while (reader.Read())
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
					list.Add(reader.Name);
			}

			return list;
		}

		private static IEnumerable<XmlCommentSection> ReadSectionTags(XmlReader reader)
		{
			// NOTE: The yield statement must not be used here because the compiler will
			// generate a class that references the reader.  This method will not actually 
			// get processed until the sections field is used (it holds the return value), 
			// and the reader will be invalid at that time.
			List<XmlCommentSection> list = new List<XmlCommentSection>(16);

			reader.ReadStartElement();

			while (reader.Read())
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
				{
					string name = reader.Name;
					bool multiple = false, marker = false;
					ContentItemEditModes modes = ContentItemEditModes.None;
					List<TopicType> appliesTo = new List<TopicType>(4);
					List<string> subsections = new List<string>(0);
					List<ITopicSectionAttributeData> attributes = new List<ITopicSectionAttributeData>(0);

					if (reader.HasAttributes)
					{
						while (reader.MoveToNextAttribute())
						{
							if (string.Equals(reader.NamespaceURI, "urn:docproject:xml-doc:sections-designer", StringComparison.OrdinalIgnoreCase))
							{
								string localName = reader.LocalName;

								if (string.Equals(localName, "multiple", StringComparison.Ordinal))
									multiple = reader.ReadContentAsBoolean();
								else if (string.Equals(localName, "marker", StringComparison.Ordinal))
									marker = reader.ReadContentAsBoolean();
								else if (string.Equals(localName, "editModes", StringComparison.Ordinal))
									modes = (ContentItemEditModes) Enum.Parse(typeof(ContentItemEditModes), reader.Value, true);
								else if (string.Equals(localName, "appliesTo", StringComparison.Ordinal))
								{
									foreach (string value in (IEnumerable<string>) StringCollectionConverter.Default.ConvertFromString(reader.Value))
									{
										TopicType type = (TopicType) Enum.Parse(typeof(TopicType), value, true);

										if (!appliesTo.Contains(type))
											appliesTo.Add(type);
									}
								}
								else if (string.Equals(localName, "subsections", StringComparison.Ordinal))
								{
									foreach (string value in (IEnumerable<string>) StringCollectionConverter.Default.ConvertFromString(reader.Value))
									{
										if (!subsections.Contains(value))
											subsections.Add(value);
									}
								}
								else
								{
									IXmlLineInfo line = (IXmlLineInfo) reader;

									throw new XmlException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
										Resources.Errors.InvalidXmlDocConfigSectionAttribute, reader.Name), null,
										line.LineNumber, line.LinePosition);
								}
							}
							else
							{
								string attributeName = reader.Name;
								TopicSectionAttributeType type = (TopicSectionAttributeType) Enum.Parse(typeof(TopicSectionAttributeType), reader.Value, true);

								attributes.Add(new XmlCommentSectionAttributeData(attributeName, type));
							}
						}

						reader.MoveToElement();
					}

					list.Add(new XmlCommentSection(name, false, attributes, multiple, marker, appliesTo, modes, subsections));
				}
			}

			return list;
		}

		protected override bool OnDeserializeUnrecognizedElement(string elementName, System.Xml.XmlReader reader)
		{
			if (reader != null)
			{
				try
				{
					if (string.Equals("format", elementName, StringComparison.Ordinal))
					{
						using (XmlReader formatReader = reader.ReadSubtree())
						{
							formatTags = ReadFormatTags(formatReader);
						}

						return true;
					}

					if (string.Equals("sections", elementName, StringComparison.Ordinal))
					{
						using (XmlReader sectionsReader = reader.ReadSubtree())
						{
							sections = ReadSectionTags(sectionsReader);
						}

						return true;
					}
				}
				catch (XmlException)
				{
					throw;
				}
				catch (Exception ex)
				{
					IXmlLineInfo line = (IXmlLineInfo) reader;

					throw new XmlException(Resources.Errors.InvalidDocumentationTagsConfigMarkup, ex,
						line.LineNumber, line.LinePosition);
				}
			}

			return false;
		}

		public XmlCommentSection FindSection(string name)
		{
			foreach (XmlCommentSection section in sections)
				if (string.Equals(section.Name, name, StringComparison.Ordinal))
					return section;

			return null;
		}
		#endregion
	}
}

